package com.xjgz.aio.socket.transport;

import com.xjgz.aio.socket.MessageProcessor;
import com.xjgz.aio.socket.Protocol;
import com.xjgz.aio.socket.StateMachineEnum;
import com.xjgz.aio.socket.buffer.BufferPagePool;
import com.xjgz.aio.socket.buffer.VirtualBuffer;
import com.xjgz.aio.socket.enhance.EnhanceAsynchronousChannelProvider;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketOption;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.security.InvalidParameterException;
import java.util.Map;
import java.util.function.Supplier;

/**
 * AIO服务端
 */
public final class AioQuickServer {

    private AsynchronousServerSocketChannel serverSocketChannel;

    private AsynchronousChannelGroup asynchronousChannelGroup;

    private boolean lowMemory = true;

    private final IoServerConfig config = new IoServerConfig();

    private static long threadSeqNumber;

    private BufferPagePool writeBufferPool;

    private BufferPagePool readBufferPool;

    public AioQuickServer(int port, Protocol<?> protocol, MessageProcessor<?> processor) {
        config.setPort(port);
        config.setProtocol(protocol);
        config.setProcessor(processor);
        config.setThreadNum(Runtime.getRuntime().availableProcessors());
    }

    public AioQuickServer(String host, int port, Protocol<?> protocol, MessageProcessor<?> processor) {
        this(port, protocol, processor);
        config.setHost(host);
    }

    public void start() throws IOException {
        asynchronousChannelGroup = new EnhanceAsynchronousChannelProvider(lowMemory).openAsynchronousChannelGroup(
            config.getThreadNum(),
            r -> new Thread(r, "smart-socket:Thread-" + (threadSeqNumber++))
        );
        start(asynchronousChannelGroup);
    }

    public void start(AsynchronousChannelGroup asynchronousChannelGroup) throws IOException {
        if (config.isBannerEnabled()) {
            System.out.println(
                IoServerConfig.BANNER + "\r\n :: smart-socket " + "::\t(" + IoServerConfig.VERSION + ") [port: " +
                    config.getPort() + ", threadNum:" + config.getThreadNum() + "]"
            );
            System.out.println("Technical Support:");
            System.out.println(" - Document: https://smartboot.tech]");
            System.out.println(" - Gitee: https://gitee.com/smartboot/smart-socket");
            System.out.println(" - Github: https://github.com/smartboot/smart-socket");
        }
        try {
            if (writeBufferPool == null) {
                this.writeBufferPool = BufferPagePool.DEFAULT_BUFFER_PAGE_POOL;
            }
            if (readBufferPool == null) {
                this.readBufferPool = BufferPagePool.DEFAULT_BUFFER_PAGE_POOL;
            }

            this.serverSocketChannel = AsynchronousServerSocketChannel.open(asynchronousChannelGroup);
            if (config.getSocketOption() != null) {
                for (Map.Entry<SocketOption<Object>, Object> entry : config.getSocketOption().entrySet()) {
                    this.serverSocketChannel.setOption(entry.getKey(), entry.getValue());
                }
            }

            if (config.getHost() != null) {
                this.serverSocketChannel
                    .bind(new InetSocketAddress(config.getHost(), config.getPort()), config.getBacklog());
            } else {
                this.serverSocketChannel.bind(new InetSocketAddress(config.getPort()), config.getBacklog());
            }

            startAcceptThread();
        } catch (IOException e) {

        }
    }

    private void startAcceptThread() {
        Supplier<VirtualBuffer> readBufferSupplier =
            () -> readBufferPool.allocateSequentially(config.getReadBufferSize());
        serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {

            @Override
            public void completed(AsynchronousSocketChannel channel, Void attachment) {
                try {
                    serverSocketChannel.accept(attachment, this);
                } catch (Throwable e) {
                    config.getProcessor().stateEvent(null, StateMachineEnum.ACCEPT_EXCEPTION, e);
                    failed(e, attachment);
                    serverSocketChannel.accept(attachment, this);
                } finally {
                    createSession(channel, readBufferSupplier);
                }
            }

            @Override
            public void failed(Throwable exc, Void attachment) {
                exc.printStackTrace();
            }
        });
    }

    /**
     * 为每个新建立的连接创建一个Session
     * 
     * @param channel
     * @param readBufferSupplier
     */
    private void createSession(AsynchronousSocketChannel channel, Supplier<VirtualBuffer> readBufferSupplier) {

    }

    public void shutdown() {
        try {
            if (serverSocketChannel != null) {
                serverSocketChannel.close();
                serverSocketChannel = null;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (asynchronousChannelGroup != null) {
            asynchronousChannelGroup.shutdown();
        }
    }

    public AioQuickServer setReadBufferSize(int size) {
        this.config.setReadBufferSize(size);
        return this;
    }

    public final AioQuickServer setBannerEnabled(boolean bannerEnabled) {
        this.config.setBannerEnabled(bannerEnabled);
        return this;
    }

    public final AioQuickServer setOption(SocketOption<Object> socketOption, Object value) {
        this.config.setSocketOption(socketOption, value);
        return this;
    }

    public final AioQuickServer setThreadNum(int threadNum) {
        if (threadNum <= 1) {
            throw new InvalidParameterException("threadNum must >= 2");
        }
        this.config.setThreadNum(threadNum);
        return this;
    }

    /**
     * 设置输出缓存区容量
     * 
     * @param bufferSize     单个内存块大小
     * @param bufferCapacity 内存块数量
     */
    public AioQuickServer setWriteBuffer(int bufferSize, int bufferCapacity) {
        config.setWriteChunkSize(bufferSize);
        config.setWriteChunkCount(bufferCapacity);
        return this;
    }

    public final AioQuickServer setBacklog(int backlog) {
        config.setBacklog(backlog);
        return this;
    }

    public AioQuickServer setBufferPagePool(BufferPagePool bufferPagePool) {
        return setBufferPagePool(bufferPagePool, bufferPagePool);
    }

    public AioQuickServer setBufferPagePool(BufferPagePool readBufferPool, BufferPagePool writeBufferPool) {
        this.readBufferPool = readBufferPool;
        this.writeBufferPool = writeBufferPool;
        return this;
    }

    /**
     * 禁用低内存模式
     */
    public AioQuickServer disableLowMemory() {
        this.lowMemory = false;
        return this;
    }
}
